import backtrader as bt

class CDemoStrategy(bt.Strategy):
    """
    学习 backtrader 定义的策略
    """

    params = (('myparam', 27), 
              ('exitbars', 5), 
              ("maperiod", 20))

    def log(self, txt, dt=None):
        ''' 提供记录功能'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        ## 将大脑加载的数据更新到dataclose属性中（注意，这是一个列表，保存股票回测开始时间到结束时间的所有close数据）。 
        ## self.datas[0]指向的是大脑通过cerebro.adddata函数加载的第一个数据
        ## 如果加载了多个数据，则可以依次使用 self.datas[1]、self.datas[2]、self.datas[n]进行访问
        ## self.dataclose 指向的是close （收盘价）line
        self.dataclose = self.datas[0].close

        ## 跟踪订单
        self.order = None

        self.buy_price = None
        self.buy_comm = None

        ## 添加一个移动平均线指标(indicator)
        self.sma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)

    def __simple_strategy__(self):
        if not self.position:
            """
            当前没有持仓
            """
            if self.dataclose[0] < self.dataclose[-1] and \
                self.dataclose[-1] < self.dataclose[-2] and \
                self.dataclose[-2] < self.dataclose[-3]:
                ## 当天收盘价低于上一个交易日收盘价，并且上一个交易日收盘价低于上两日收盘价
                ## 也就是股价连跌了两天
                self.log("BUY CREATE, %.2f" % self.dataclose[0])
                # 记录创建的订单，避免订单没有处理完成时创建新的订单
                self.order = self.buy()
        else:
            # 当前有持仓，持仓5天卖出
            # 在python中，len通常返回的一个列表中数据的多少，而在backtrader中，重写了len函数，返回的是已经处理过数据行（也就是Bar）
            if len(self) > (self.bar_executed + self.params.exitbars):
                self.log("SELL CREATE, %.2f" % self.dataclose[0])

                # 记录创建的订单，避免订单没有处理完成时创建新的订单
                self.order = self.sell()

    def __sma_strategy__(self):
        if not self.position:
            if self.dataclose[0] > self.sma[0]:
                # 大于均线就买
                self.log("BUY CREATE, %.2f" % self.dataclose[0])
                # 记录创建的订单，避免订单没有处理完成时创建新的订单
                self.order = self.buy()
        else:
            if self.dataclose[0] < self.sma[0]:
                # 小于均线就卖
                self.log("SELL CREATE, %.2f" % self.dataclose[0])

                # 记录创建的订单，避免订单没有处理完成时创建新的订单
                self.order = self.sell()

    def next(self):
        """
        next方法是Strategy最重要的的方法，具体策略的实现都在这个函数中
        """
        self.log("Close: %.2f" % self.dataclose[0])

        if self.order:
            """
            是否有一个未处理的订单（委托），如果有则不能创建新的订单
            """
            return
        
        self.__sma_strategy__()

    def notify_order(self, order):
        """
        订单状态变化通知
        """
        if order.status in [order.Accepted, order.Submitted]:
            """
            订单提交给了券商或者订单被券商接收，什么都不做
            """
            return
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log("BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm: %.2f" % 
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
                self.buy_price = order.executed.price
                self.buy_comm = order.executed.comm
            elif order.issell():
                self.log("SELL EXECUTED, , Price: %.2f, Cost: %.2f, Comm: %.2f" % 
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))

            """
            这里使用了bar这个概念，没有包含任何时间的概念，也就是一个bar，可以是1分钟，1个小时，也可以是一天，一个月，
            这些基于你输入的数据，如果你输入的股票每小时（分时）数据，那么一个bar就是一分钟，如果提供是周K数据，一个bar就是一周。
            本例中，我们获取的数据基于每天，那么一个bar就是一天。
            """
            self.bar_executed = len(self)
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log("Order Canceled/Margin/Rejected")
        
        self.order = None

    def notify_trade(self, trade):
        """
        订单成交后执行函数
        """
        #self.log("ORDER TRADE")
        if not trade.isclosed:
            ## 没有平仓，不处理
            ## 开仓、平仓
            return
        
        # 记录下盈利数据（Cross：毛利；Net:净利）
        self.log("OPERATION PROFIT, CROSS: %.2f, NET: %.2f" % (trade.pnl, trade.pnlcomm))


    def stop(self):
        self.log("(MA period %d), Ending value %.2f" %(self.params.maperiod, self.broker.broker.getvalue()))